<?php

class gl extends raster
{
	const Projection = "Projection";
	const ModelView = "ModelView";
	
	public $mode;
	
	public $matrixList;
	
	public $stack = array();
	
	/**
	 * View Port
	 *
	 * @var ViewPort
	 */
	public $viewPort;
	
	public function __construct()
	{
		$this->mode = gl::ModelView;
		$this->matrixList[gl::Projection] = self::Identity();
		$this->matrixList[gl::ModelView] = self::Identity();
		$this->viewPort = new ViewPort();
	}
	
	public function push()
	{
		array_push($this->stack,$this->matrixList[$this->mode]);
	}
	
	public function pop()
	{
		$this->matrixList[$this->mode] = array_pop($this->stack);
	}
	
	public function setMode($mode)
	{
		$this->mode = $mode;
	}
	
	public function setViewPort(ViewPort $viewPort)
	{
		$this->viewPort = $viewPort;
	}
	
	public function matrixMultiply(cMatrix $matrix)
	{
		//echo $this->matrixList[$this->mode]->__toString();
		//echo $matrix->__toString();
		$this->matrixList[$this->mode] = cMatrix::mul($this->matrixList[$this->mode],$matrix);
		//echo $this->matrixList[$this->mode]->__toString();die;
	}
	
	public function matrixLoad(cMatrix $matrix)
	{
		$this->matrixList[$this->mode] = $matrix;
	}
	
	public function processPoint(cPoint3 $point)
	{
		$eyeCoords = cMatrix::mul($this->matrixList[gl::ModelView],$point);
		$clipCoords = cMatrix::mul($this->matrixList[gl::Projection],$eyeCoords);
		$normDevCoords = $this->performClipW($clipCoords);
		$viewPortCoord = $this->scaleToViewPort($normDevCoords);
		return $viewPortCoord; 
	}
	
	private function performClipW(cMatrix $point)
	{
		$w = $point->get(0,3);
		$point = new cPoint3($point->get(0,0)/$w,$point->get(0,1)/$w,$point->get(0,2)/$w,$w);
		return $point;
	}
	
	private function scaleToViewPort(cMatrix $point)
	{
		$point = new cPoint3(	($point->get(0,0)+1) * ($this->viewPort->width / 2) + $this->viewPort->x,
								($point->get(0,1)+1) * ($this->viewPort->height / 2) + $this->viewPort->y,
								$point->get(0,2),
								$point->get(0,3));
		return $point;
	}
	
	public function rotate4f($angle,$x,$y,$z)
	{		
		$matrix = new cMatrix(4,4);
		$c = cos($angle);
		$c1 = 1 - $c;
		$s = sin($angle);
		$xx = $x * $x;
		$xy = $x * $y;
		$xz = $x * $z;
		$yy = $y * $y;
		$yz = $y * $z;
		$zz = $z * $z;
		$xs = $x * $s;
		$zs = $z * $s;
		$ys = $y * $s;
		
		$matrix->data[0] = array($xx * $c1 + $c,	$xy * $c1 - $zs,		$xz * $c1 - $ys,	0);
		$matrix->data[1] = array($xy * $c1 + $zs,	$yy * $c1 + $c,			$yz * $c1 - $xs,	0);
		$matrix->data[2] = array($xz * $c1 - $ys,	$yz * $c1 + $xs,		$zz * $c1 + $c,		0);
		$matrix->data[3] = array(0,0,0,1);

		$this->MatrixMultiply($matrix);
	}
	
	public function rotateV($angle,cPoint3 $point)
	{
		$this->rotate4f($angle,$point->getX(),$point->getY(),$point->getZ());
	}
	
	public function translate3f($x,$y,$z)
	{
		$matrix = new cMatrix(4,4);
		$matrix->data[0] = array(1,0,0,$x);
		$matrix->data[1] = array(0,1,0,$y);
		$matrix->data[2] = array(0,0,1,$z);
		$matrix->data[3] = array(0,0,0,1);
		
		$this->MatrixMultiply($matrix);				
	}
	
	public function translateV(cPoint3 $point)
	{
		$this->translate3f($point->getX(),$point->getY(),$point->getZ());
	}
	
	public function scale3f($x,$y,$z)
	{
		$matrix = new cMatrix(4,4);
		$matrix->data[0] = array($x,0,0,0);
		$matrix->data[1] = array(0,$y,0,0);
		$matrix->data[2] = array(0,0,$z,0);
		$matrix->data[3] = array(0,0,0,1);
		
		$this->MatrixMultiply($matrix);						
	}
	
	public function scaleV(cPoint3 $point)
	{
		$this->scale3f($point->getX(),$point->getY(),$point->getZ());
	}	
	
	public static function frustum($left,$right,$bottom,$top,$near,$far)
	{
		$matrix = new cMatrix(4,4);
		
		$a = ($right + $left) / ($right - $left);
		$b = ($top + $bottom) / ($top - $bottom);
		$c = -($far + $near) / ($far - $near);
		$d = -(2 * $far * $near) / ($far - $near);
		$e = (2 * $near) / ($right - $left);
		$f = (2 * $near) / ($top - $bottom); 
		
		$matrix->data[0] = array($e,0,$a,0);
		$matrix->data[1] = array(0,$f,$b,0);
		$matrix->data[2] = array(0,0,$c,$d);
		$matrix->data[3] = array(0,0,-1,0);
		return $matrix;			
	}
	
	public static function perspective($fovy,$aspect,$zNear,$zFar)
	{
		$matrix = new cMatrix(4,4);
		
		$f = 1/tan($fovy / 2); 
		$a = (2*$zFar * $zNear) / ($zNear - $zFar);
		$b = ($zFar + $zNear) / ($zNear - $zFar);
		$c = $f/$aspect;
		
		$matrix->data[0] = array($c,0,0,0);
		$matrix->data[1] = array(0,$f,0,0);
		$matrix->data[2] = array(0,0,$b,$a);
		$matrix->data[3] = array(0,0,-1,0);
		return $matrix;			
	}
	
	public static function orthographic($left,$right,$bottom,$top,$near,$far)
	{
		$matrix = new cMatrix(4,4);
		
		$a = 2 / ($right - $left);
		$b = 2 / ($top - $bottom);
		$c = -2 / ($far - $near);
		$d = -($right + $left) / ($right - $left);
		$e = -($top + $bottom) / ($top - $bottom);
		$f = -($far + $near) / ($far - $near); 
		
		$matrix->data[0] = array($a,0,0,$d);
		$matrix->data[1] = array(0,$b,0,$e);
		$matrix->data[2] = array(0,0,$c,$f);
		$matrix->data[3] = array(0,0,0,1);
		return $matrix;			
	}
		
	public static function Identity()
	{
		$matrix = new cMatrix(4,4);
		$matrix->data[0] = array(1,0,0,0);
		$matrix->data[1] = array(0,1,0,0);
		$matrix->data[2] = array(0,0,1,0);
		$matrix->data[3] = array(0,0,0,1);
		return $matrix;					
	}

	public function drawLine(cPoint3 $v1,cPoint3 $v2,$colour)
	{
		return parent::drawLine(	$this->processPoint($v1),
									$this->processPoint($v2),
									$colour);
	}
	
	public function drawTri(cPoint3 $v1,cPoint3 $v2, cPoint3 $v3,$colour)
	{
		return parent::drawTri(		$this->processPoint($v1),
									$this->processPoint($v2),
									$this->processPoint($v3),
									$colour);		
	}
}

?>